import 'dart:convert';
import 'dart:ffi';
import 'dart:typed_data';
import 'package:esc_pos_utils_plus/gbk_codec/src/converter_gbk_byte.dart';
import 'package:ffi/ffi.dart';
import 'package:flutter_xprinter/connect_type.dart';
import 'esc_command.dart';
import 'flutter_xprinter.dart';
import 'flutter_xprinter_platform_interface.dart';
import 'package:ffi/ffi.dart' as ffi;

extension StringPointer on String {
  Pointer<Utf8> toNativeGbk({Allocator allocator = malloc}) {
    final units = gbk_bytes.encode(this);
    final Pointer<Uint8> result = allocator<Uint8>(units.length + 1);
    final Uint8List nativeString = result.asTypedList(units.length + 1);
    nativeString.setAll(0, units);
    nativeString[units.length] = 0;
    return result.cast();
  }
}

extension Uint8ListPointer on Uint8List {
  Pointer<Uint8> ptr() {
    final blob = calloc<Uint8>(length);
    final blobBytes = blob.asTypedList(length);
    blobBytes.setAll(0, this);
    return blob;
  }
}

extension ListPointer on List<int> {
  Pointer<Uint8> ptr() {
    return Uint8List.fromList(this).ptr();
  }
}


typedef CFuncNativePara = Pointer<Void> Function(Pointer<Int8>);
typedef CFuncDartPara = Pointer<Void> Function(Pointer<Int8>);

typedef CFuncNativeOpenPort = Int32 Function(Pointer<Void>, Pointer<Int8>);
typedef CFuncDartOpenPort = int Function(Pointer<Void>, Pointer<Int8>);

typedef CFuncNativeInitialize = Int32 Function(Pointer<Void>);
typedef CFuncDartInitialize = int Function(Pointer<Void>);

typedef CFuncNativePrintTextS = Int32 Function(Pointer<Void>, Pointer<Int8>);
typedef CFuncDartPrintTextS = int Function(Pointer<Void>, Pointer<Int8>);

typedef CFuncNativePrintText = Int32 Function(Pointer<Void>, Pointer<Int8>, Int32, Int32);
typedef CFuncDartPrintText = int Function(Pointer<Void>, Pointer<Int8>, int, int);

typedef CFuncNativeWriteData = Int32 Function(Pointer<Void>, Pointer<Int8>, Int32);
typedef CFuncDartWriteData = int Function(Pointer<Void>, Pointer<Int8>, int);

typedef CFuncNativeCutPaperWithDistance = Int32 Function(Pointer<Void>, Int32);
typedef CFuncDartCutPaperWithDistance = int Function(Pointer<Void>, int);

//PrintAndFeedLine
typedef CFuncNativePrintAndFeedLine = Int32 Function(Pointer<Void>);
typedef CFuncDartPrintAndFeedLine = int Function(Pointer<Void>);

typedef CFuncNativeSetRelativeHorizontal = Int32 Function(Pointer<Void>, Int32);
typedef CFuncDartSetRelativeHorizontal = int Function(Pointer<Void>, int);

typedef CFuncNativeSetAlign = Int32 Function(Pointer<Void>, Int32);
typedef CFuncDartSetAlign = int Function(Pointer<Void>, int);

typedef CFuncNativeSetTextBold = Int32 Function(Pointer<Void>, Int32);
typedef CFuncDartSetTextBold = int Function(Pointer<Void>, int);

typedef CFuncNativeSetTextFont = Int32 Function(Pointer<Void>, Int32);
typedef CFuncDartSetTextFont = int Function(Pointer<Void>, int);

typedef CFuncNativeReleasePrinter = Int32 Function(Pointer<Void>);
typedef CFuncDartReleasePrinter = int Function(Pointer<Void>);

typedef CFuncNativeSetAbsolutePrintPosition = Int32 Function(Pointer<Void>, Int32);
typedef CFuncDartSetAbsolutePrintPosition = int Function(Pointer<Void>, int);

/// An implementation of [FlutterXprinterPlatform] that uses method channels.
class FlutterXprinterWin extends FlutterXprinterPlatform {
  /// The method channel used to interact with the native platform.
  var _handlers = [];
  var _printer;
  var _mInitPrinter;
  var _mOpenPort;
  var _mInitialize;
  var _mPrintTextS;
  var _mPrintAndFeedLine;
  var _mCutPaperWithDistance;
  var _mSetAlign;
  var _mSetTextBold;
  var _mSetTextFont;
  var _mReleasePrinter;
  var _mPrintText;
  var _mSetRelativeHorizontal;
  var _mSetAbsolutePrintPosition;
  var _mWriteData;

  FlutterXprinterWin() {
    var dll = DynamicLibrary.open("data/flutter_assets/packages/flutter_xprinter/windows/printer.dll");
    _mInitPrinter = dll.lookupFunction<CFuncNativePara, CFuncDartPara>("InitPrinter");
    _mOpenPort = dll.lookupFunction<CFuncNativeOpenPort, CFuncDartOpenPort>("OpenPort");
    _mInitialize = dll.lookupFunction<CFuncNativeInitialize, CFuncDartInitialize>("PrinterInitialize");
    _mPrintTextS = dll.lookupFunction<CFuncNativePrintTextS, CFuncDartPrintTextS>("PrintTextS");
    _mPrintText = dll .lookupFunction<CFuncNativePrintText, CFuncDartPrintText>("PrintText");
    _mPrintAndFeedLine = dll.lookupFunction<CFuncNativePrintAndFeedLine, CFuncDartPrintAndFeedLine>("PrintAndFeedLine");
    _mCutPaperWithDistance = dll.lookupFunction<CFuncNativeCutPaperWithDistance, CFuncDartCutPaperWithDistance>("CutPaperWithDistance");
    _mSetAlign = dll.lookupFunction<CFuncNativeSetAlign, CFuncDartSetAlign>("SetAlign");
    _mSetTextBold = dll.lookupFunction<CFuncNativeSetTextBold, CFuncDartSetTextBold>("SetTextBold");
    _mSetTextFont = dll.lookupFunction<CFuncNativeSetTextFont, CFuncDartSetTextFont>("SetTextFont");
    _mReleasePrinter = dll.lookupFunction<CFuncNativeReleasePrinter, CFuncDartReleasePrinter>("ReleasePrinter");
    _mWriteData = dll.lookupFunction<CFuncNativeWriteData, CFuncDartWriteData>("WriteData");
    _mSetRelativeHorizontal = dll.lookupFunction<CFuncNativeSetRelativeHorizontal, CFuncDartSetRelativeHorizontal>("SetRelativeHorizontal");
    _mSetAbsolutePrintPosition = dll.lookupFunction<CFuncNativeSetAbsolutePrintPosition, CFuncDartSetAbsolutePrintPosition>("SetAbsolutePrintPosition");
  }

  @override
  Future<String?> getPlatformVersion() {
    return Future.value("win 10");
  }

  @override
  void addMethodCallHandler(CallHandler handler) {
    _handlers.add(handler);
  }

  @override
  void connectUSB(String pathName) {
    var result = _connect("USB", pathName);
    for (var element in _handlers) {
      element({"code": result, "usb": pathName});
    }
  }

  @override
  void connectNet(String ipAddress) {
    var result = _connect("NET", ipAddress);
    for (var element in _handlers) {
      element({"code": result, "net": ipAddress});
    }
  }

  int _connect(String type, String address) {
    if (_printer != null) {
      disconnect(address);
    }
    _printer = _mInitPrinter("".toNativeUtf16().cast<Int8>());
    return _mOpenPort(_printer, "$type,$address".toNativeUtf16().cast<Int8>());
  }


  void printerInitialize() {
    _mInitialize(_printer);
  }

  @override
  Future<int> write(List<int> bytes, ConnectType type, String device) async{
    switch(type) {
      case ConnectType.TYPE_USB:
        connectUSB(device);
      case ConnectType.TYPE_NET:
        connectNet(device);
      case ConnectType.TYPE_BLE:
        throw Exception("windows not support BLE");
    }
    printerInitialize();
    int result = _mWriteData(_printer, bytes.ptr(), bytes.length);
    disconnect(device);
    return result;
  }

  @override
  void disconnect(String address) {
    _mReleasePrinter(_printer);
  }
}
